﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Verifications.vers
{
    /// <summary>
    /// A powerful log manager
    /// </summary>
    public class HWLog
    {
        /// <summary>
        /// 代码所在文件的全路径，保留，暂时未启动。
        /// </summary>
        public string Filepath = default!;

        /// <summary>
        /// 代码所在的类名。
        /// </summary>
        public string ClassName = default!;

        /// <summary>
        /// 代码所在的方法名。
        /// </summary>
        public string MethodName = default!;

        /// <summary>
        /// 代码所在行的全路径，格式为 命名空间.类名.方法.行号，如 Verifications.vers.HWLog.SetLogLevel.75
        /// </summary>
        public string Path = default!;

        /// <summary>
        /// 代码所在的行号。
        /// </summary>
        public int LineNumber = -1;

        /// <summary>
        /// 日志消息的内容
        /// </summary>
        public string Message = default!;

        /// <summary>
        /// 日志的类型。
        /// </summary>
        public LogLevel LogLevel = 0;


        /// <summary>
        /// 日志列表缓存，通过 SaveLogs() 方法写入到文件。
        /// </summary>
        public static List<HWLog> logs = new List<HWLog>();

        /// <summary>
        /// 是否自动写入，默认为 true，每隔 AutoSaveInterval 秒， 若 logs 不为空则调用 SaveLogs() 一次。 
        /// </summary>
        public static bool IsAutoSaveAvailable = true;

        /// <summary>
        /// 默认的日志文件为当前程序目录下的 LogFile.txt 
        /// </summary>
        public static string LogFile = "logs.txt";

        /// <summary>
        /// 指定自动保存的时间间隔，单位为秒，默认值为3。
        /// </summary>
        public static int AutoSaveInterval = 3;

        /// <summary>
        /// 输出级别，只有满足条件的日志才会输出。
        /// </summary>
        public static LogLevel LogOutputLevel = LogLevel.ALL;

        /// <summary>
        /// 输出条件列表
        /// </summary>
        public static List<LogCondition> conditions = new List<LogCondition>();

        /// <summary>
        /// 添加可以输出的路径。
        /// </summary>
        /// <param name="path">可以输出的路径，格式为 namespace.classname.methodname</param>
        /// <param name="isPositiveCondition">指定为true时生效还是为false时生效，默认为 true。</param>
        public static void AddPath(string path, bool isPositiveCondition = true)
        {
            conditions.Add(new LogCondition(ConditionType.PATH, path, isPositiveCondition));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="level"></param>
        public static void SetLogLevel(LogLevel level = LogLevel.ALL)
        {
            LogOutputLevel = level;
        }


        /// <summary>
        /// 默认静态构造函数，只要使用此类就会优先调用一次本函数，作用是启动一个自动保存日志的函数。
        /// </summary>
        static HWLog()
        {
            Task.Run(() =>
            {
                while (true)
                {
                    if (IsAutoSaveAvailable && logs.Count > 0)
                    {
                        SaveLogs();
                        Console.WriteLine("saved.");
                    }

                    Thread.Sleep(TimeSpan.FromSeconds(3));
                }
            });
        }


        public HWLog() { }

        public HWLog(string content, LogLevel logLevel = LogLevel.INFO, int traceLevel = 1)
        {
            StackFrame st = new System.Diagnostics.StackTrace(traceLevel, true).GetFrame(0);
            LogLevel = logLevel;
            Filepath = st.GetFileName();
            ClassName = st.GetMethod().DeclaringType.FullName;
            MethodName = st.GetMethod().Name;
            LineNumber = st.GetFileLineNumber();
            Path = $"{ClassName}.{MethodName}.{LineNumber}";
            Message = content;
        }

        /// <summary>
        /// 将日志 logs 保存到文件 LogFile 中。
        /// </summary>
        public static void SaveLogs()
        {
            lock (logs)
            {
                StringBuilder sb = new StringBuilder();
                foreach (var item in logs)
                {
                    sb.AppendLine(item.ToString());
                }
                logs.Clear();
                File.AppendAllText(LogFile, sb.ToString());
            }
        }


        /// <summary>
        /// 格式为 2022/06/15 10:09:46 2 Verifications.vers.HWLog::Log@114 -> hello2
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return $"{DateTime.Now:yyyy/MM/dd HH:mm:ss}\t{Path}\t{LogLevel}\t{Message}";
        }

        /// <summary>
        /// 记录日志，首先会添加到后台的 logs 中，然后再定时保存。
        /// </summary>
        /// <param name="content">日志内容。</param>
        /// <param name="logLevel">日志等级。</param>
        /// <param name="traceLevel">追踪等级，主要用于表示函数的嵌套层次。</param>
        /// <returns></returns>
        public static HWLog Log(string content, LogLevel logLevel = LogLevel.INFO, int traceLevel = 1)
        {
            var log = new HWLog(content, logLevel, traceLevel + 1);

            if ((LogOutputLevel & log.LogLevel) > 0 && ValidateConditions(log))
            {
                logs.Add(log);
                Console.WriteLine(log);
            }
            return log;
        }

        /// <summary>
        /// 写入FATAL类日志。
        /// </summary>
        /// <param name="content">日志内容。</param>
        /// <returns></returns>
        public static HWLog Fatal(string content)
        {
            return Log(content, LogLevel.FATAL, 2);
        }

        /// <summary>
        /// 写入ERROR类日志。
        /// </summary>
        /// <param name="content">日志内容。</param>
        /// <returns></returns>
        public static HWLog Error(string content)
        {
            return Log(content, LogLevel.ERROR, 2);
        }

        /// <summary>
        /// 写入WARN类日志。
        /// </summary>
        /// <param name="content">日志内容。</param>
        /// <returns></returns>
        public static HWLog Warn(string content)
        {
            return Log(content, LogLevel.WARN, 2);
        }

        /// <summary>
        /// 写入INFO类日志。
        /// </summary>
        /// <param name="content">日志内容。</param>
        /// <returns></returns>
        public static HWLog Info(string content)
        {
            return Log(content, LogLevel.INFO, 2);
        }

        /// <summary>
        /// 写入DEBUG类日志。
        /// </summary>
        /// <param name="content">日志内容。</param>
        /// <returns></returns>
        public static HWLog Debug(string content)
        {
            return Log(content, LogLevel.DEBUG, 2);
        }

        /// <summary>
        /// 验证当前日志是否能通过测试条件，当测试条件数量为0时，返回 true.
        /// </summary>
        /// <param name="log">待测日志。</param>
        /// <returns></returns>
        private static bool ValidateConditions(HWLog log)
        {
            return conditions.Count == 0 ? true : conditions.Any(condition => condition.Satisfy(log));
        }
    }

    /// <summary>
    /// 日志类型根据重要程度由高到底分为 FATAL, ERROR, WARN, INFO, DEBUG 5种。
    /// 为了便于操作，还设置了 OFF 和 ALL 两种表示全半或全开。
    /// </summary>
    public enum LogLevel
    {
        /// <summary>
        /// 关闭所有日志。
        /// </summary>
        OFF = 0,
        /// <summary>
        /// 系统崩溃级错误，在系统出现重大错误时由 try-catch 捕捉。
        /// </summary>
        FATAL = 1,
        /// <summary>
        /// 一般非功能性的错误，如数据格式不正确，连接失败，文件不存在等。
        /// </summary>
        ERROR = 2,
        /// <summary>
        /// 一些可能的错误，哪数值异常，没有保存就关闭等。
        /// </summary>
        WARN = 4,
        /// <summary>
        /// 一般系统信息，如果打开时间，操作日志等。
        /// </summary>
        INFO = 8,
        /// <summary>
        /// 表示除DEBUG外的，FATAL - INFO 的所有日志
        /// </summary>
        RUN = 15,  
        /// <summary>
        /// 调试用信息。
        /// </summary>
        DEBUG = 16,
        /// <summary>
        /// 打开所有类型开关，目前只有FATAL, ERROR, WARN, INFO 和 DEBUG 五类。
        /// </summary>
        ALL = 255
    }

    /// <summary>
    /// 用于区别日志输出条件类。
    /// </summary>
    public class LogCondition
    {
        /// <summary>
        /// 日志条件类型。
        /// </summary>
        public ConditionType ConditionType = ConditionType.PATH;

        /// <summary>
        /// 路径，如 Verifications.vers.HWLog.SetLogLevel.75
        /// </summary>
        public string Path = "Verifications.Program";

        /// <summary>
        /// 是否是为 true 时生效，默认为 true，暂时未考虑为 false 的场景。
        /// </summary>
        public bool IsPositive = true;

        /// <summary>
        /// 日志等级，默认为 WARN。
        /// </summary>
        public LogLevel level = LogLevel.WARN;

        /// <summary>
        /// 日志类型。
        /// </summary>
        public LogCondition() { } 

        /// <summary>
        /// 日志类型。
        /// </summary>
        /// <param name="type">日志的类型。</param>
        /// <param name="fullpath">日志路径。</param>
        /// <param name="isPositiveCondition">是否是为true时生效，默认值为true。</param>
        public LogCondition(ConditionType type, string fullpath, bool isPositiveCondition = true)
        {
            this.ConditionType = type;
            this.Path = fullpath;
            this.IsPositive = isPositiveCondition;
        }

        /// <summary>
        /// 验证当前日志是否符合条件，true表
        /// </summary>
        /// <param name="log"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public bool Satisfy(HWLog log)
        {
            bool satisfied = false;
            if (IsPositive)
            {
                if (ConditionType == ConditionType.PATH)
                {
                    satisfied = log.Path.StartsWith(Path);
                } 
            }

            return satisfied;
        }
    }

    public enum ConditionType
    {
        /// <summary>
        /// 未指定
        /// </summary>
        UNKNOWN = 0,
        /// <summary>
        /// 路径，如 Verifications.vers.HWLog.SetLogLevel.75。
        /// </summary>
        PATH = 1,
        /// <summary>
        /// 表示代码所在的文件名。
        /// </summary>
        FILE = 2,
        /// <summary>
        /// 表示所有类型。
        /// </summary>
        ALL = 255,
    }
}
